Skip to content

feat: initial MCP server scaffold (authzen_evaluate)#1

Merged
kanywst merged 8 commits into
mainfrom
feat/initial-scaffold
May 27, 2026
Merged

feat: initial MCP server scaffold (authzen_evaluate)#1
kanywst merged 8 commits into
mainfrom
feat/initial-scaffold

Conversation

@kanywst

@kanywst kanywst commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Adds authzen_evaluate MCP tool that POSTs to an OpenID AuthZEN 1.0 PDP and returns the decision
  • PDP endpoint configurable via AUTHZEN_PDP_URL env or per-call pdp_url arg
  • 5 unit tests against a fake PDP cover allow / deny / missing config / malformed JSON / 5xx response
  • goreleaser config emits cross-compiled archives, CycloneDX SBOMs (syft), keyless cosign-signed checksums
  • CI workflow runs go vet, go test -race, golangci-lint; release workflow triggers on v* tags

Test plan

  • go vet ./... passes locally
  • go test -race ./... passes (5/5)
  • CI green on this PR
  • Manual: tag v0.1.0 after merge; verify cosign-signed checksums verify with the documented cosign verify-blob command

Summary by CodeRabbit

  • New Features

    • Added MCP server with AuthZEN authorization evaluation tool, accepting subject, resource, and action parameters to retrieve policy decisions from a configured endpoint.
  • Chores

    • Set up project module with Go 1.26.3 and required dependencies.
    • Configured automated CI pipeline for testing and linting.
    • Configured automated release pipeline with binary builds for macOS and Linux across multiple architectures.
  • Tests

    • Added comprehensive test coverage for authorization evaluation scenarios including allow/deny decisions, error handling, and PDP endpoint validation.

Review Change Stack

Provides the `authzen_evaluate` tool: POST a subject/resource/action/context
bundle to the configured PDP and return the AuthZEN decision JSON.

- PDP endpoint configurable via AUTHZEN_PDP_URL env or per-call pdp_url arg
- Unit tests cover allow/deny, missing PDP, malformed JSON, 5xx upstream
- goreleaser config with SBOM (syft) and keyless cosign signing
- CI workflow (vet, test -race, golangci-lint) + release workflow
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@kanywst, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 28 minutes and 27 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6d03d81b-8049-4a33-9e05-c673bf58dfd0

📥 Commits

Reviewing files that changed from the base of the PR and between 2d97a2e and f8e2400.

📒 Files selected for processing (9)
  • .github/dependabot.yml
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .goreleaser.yml
  • Makefile
  • README.md
  • main.go
  • main_test.go
  • scripts/smoke.sh
📝 Walkthrough

Walkthrough

A new MCP server (mcp-authzen) is added from scratch with Go module setup, MCP tool implementation, comprehensive test coverage, and full CI/CD automation including testing, linting, cross-platform builds, SBOM generation, and cosign-based release signing.

Changes

MCP AuthZEN Server Implementation

Layer / File(s) Summary
Module and dependency setup
go.mod
Go module declaration for github.com/0-draft/mcp-authzen pinning github.com/mark3labs/mcp-go at v0.54.1 and transitive dependencies.
MCP server and evaluate tool implementation
main.go
Core data types (authzenRequest, authzenResponse), MCP server entry point with --version and --help CLI support, authzen_evaluate tool registration with argument schema, and evaluate function that parses JSON arguments, resolves PDP URL from args or environment, POSTs the request to the AuthZEN endpoint, and returns formatted decision/context or error.
Server behavior and error handling tests
main_test.go
Test helpers, fake PDP httptest server, and five test cases validating allow/deny decisions, PDP URL configuration errors, malformed JSON, and HTTP 5xx failures.
CI/CD and release automation
.github/workflows/ci.yml, .github/workflows/release.yml, .goreleaser.yml
CI workflow running go vet and go test -race on push to main and pull requests; release workflow triggered on tag pushes that builds, signs, and uploads cross-platform binaries via GoReleaser with SBOM generation and cosign-based checksum signing.

Sequence Diagram

sequenceDiagram
  participant Client as MCP Client
  participant Server as mcp-authzen<br/>(MCP Server)
  participant PDP as AuthZEN PDP<br/>(HTTP endpoint)
  Client->>Server: CallToolRequest<br/>(authzen_evaluate)
  Server->>Server: parseJSONArg<br/>(subject, resource, action)
  Server->>Server: resolve PDP URL<br/>(pdp_url or env)
  Server->>PDP: POST authzenRequest<br/>(subject, resource, action)
  PDP-->>Server: JSON response<br/>(decision, context)
  Server->>Server: unmarshal & format<br/>response
  Server-->>Client: CallToolResult<br/>(decision + context)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A rabbit hops in, code so clean and bright,
MCP tools and AuthZEN's might,
Tests and pipelines, CI/CD flow,
Release with a signature, watch it grow!
From source to binary, signed and sure. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: initial MCP server scaffold (authzen_evaluate)' clearly and concisely summarizes the primary change—introducing an initial MCP server implementation with the authzen_evaluate tool. It is specific, descriptive, and directly reflects the main changeset objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/initial-scaffold

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces mcp-authzen, a Model Context Protocol (MCP) server that acts as a gateway to an OpenID AuthZEN 1.0 compliant Policy Decision Point (PDP). It exposes the authzen_evaluate tool to allow LLM agents to query authorization decisions. The feedback identifies several key improvements: downgrading the unreleased Go version (1.26.3) in go.mod to a stable release, adding Windows compilation support in GoReleaser, securing PDP requests with an authorization token, preventing potential memory exhaustion by limiting the response reader size, and optimizing JSON validation using json.Valid.

Comment thread go.mod
Comment thread .goreleaser.yml
Comment thread main.go
Comment thread main.go Outdated
Comment thread main.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
main_test.go (1)

42-49: ⚡ Quick win

Assert outbound HTTP contract in fakePDP.

Add method/content-type checks so tests fail if evaluate() stops sending proper AuthZEN POST JSON requests.

Suggested test hardening diff
 	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		if r.Method != http.MethodPost {
+			http.Error(w, "expected POST", http.StatusMethodNotAllowed)
+			return
+		}
+		if ct := r.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
+			http.Error(w, "expected application/json", http.StatusUnsupportedMediaType)
+			return
+		}
 		body, _ := io.ReadAll(r.Body)
 		if capture != nil {
 			_ = json.Unmarshal(body, capture)
 		}
 		w.Header().Set("Content-Type", "application/json")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@main_test.go` around lines 42 - 49, The fakePDP test server should assert the
outbound HTTP contract: inside the httptest handler created by fakePDP (the
anonymous handler passed to httptest.NewServer), validate r.Method == "POST" and
that r.Header.Get("Content-Type") contains "application/json", returning a
400/appropriate error if not, then read and json.Unmarshal the body into capture
as before; this ensures calls from evaluate() must be POST with JSON
content-type and will cause the test to fail if evaluate() stops sending proper
AuthZEN POST JSON requests.
.github/workflows/release.yml (1)

35-35: ⚡ Quick win

Pin GoReleaser version instead of latest.

Line 35 uses version: latest; pinning a specific version makes releases reproducible and avoids surprise breakage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml at line 35, The workflow step currently sets
the GoReleaser action with the key "version: latest"; update that to pin a
specific released tag (or full SHA) of the GoReleaser action instead of "latest"
so releases are reproducible—locate the workflow step containing the "version"
key and change the value from "latest" to a concrete tag like a vX.Y.Z release
(or immutable commit SHA).
.github/workflows/ci.yml (1)

31-31: ⚡ Quick win

Avoid floating lint tool versions in CI.

Line 31 uses version: latest, which can break pipelines unpredictably; pin a specific golangci-lint version for reproducible CI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml at line 31, The CI workflow currently pins the
linter to "version: latest" which causes non-reproducible runs; replace the
"version: latest" value used for the golangci-lint step with a specific released
tag (e.g., a stable semver like v1.59.0) so the workflow always uses a known
tool version, and update any related references to the same tag (the "version:
latest" literal in the golangci-lint job/step) to ensure reproducible CI.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 15-16: CI workflow uses floating action tags and leaves checkout
credentials enabled; replace the three action references actions/checkout@v6,
actions/setup-go@v6, and golangci/golangci-lint-action@v6 with their
corresponding full commit SHAs to pin to immutable versions, and add
persist-credentials: false to each actions/checkout step (the occurrences of
actions/checkout in the file) so the runner does not expose repo credentials to
subsequent steps.

In @.github/workflows/release.yml:
- Around line 17-19: The checkout step using actions/checkout@v6 currently sets
fetch-depth: 0 but leaves credentials persisted; update the checkout step (the
actions/checkout@v6 block) to include persist-credentials: false under the with:
section so the runner does not retain a writable token in local git config (keep
the existing fetch-depth: 0 and add persist-credentials: false alongside it).
- Line 17: Replace the tag-based action references with immutable commit SHAs
for each `uses:` entry (specifically the occurrences of actions/checkout@v6,
actions/setup-go@v6, anchore/sbom-action/download-syft@v0,
sigstore/cosign-installer@v3, and goreleaser/goreleaser-action@v7): look up the
corresponding GitHub repository for each action, find the commit SHA that
matches the tag currently used, and replace the tag (e.g., actions/checkout@v6)
with the full 40-character SHA form (e.g., actions/checkout@<full-sha>) in the
release workflow; ensure you use the exact repo/name@<sha> syntax and verify the
workflow runs successfully after updating.

In @.goreleaser.yml:
- Line 7: Replace the mutating before.hooks command "go mod tidy" with a
non-mutating check such as "go mod verify" in the .goreleaser.yml before.hooks
entry: locate the before.hooks key that currently runs "go mod tidy" and change
it to run "go mod verify" (and keep any "go mod tidy" usage confined to
CI/development enforcement scripts instead of the release hook).

In `@main.go`:
- Around line 114-121: Validate the pdp_url value before using it: parse pdpURL
with net/url.Parse, ensure url.Scheme is exactly "http" or "https", and ensure
url.Hostname() is non-empty (reject empty host or malformed URLs), and return
the existing mcp.NewToolResultError when validation fails. Apply the same checks
to both uses of the pdpURL variable (the initial block that reads
pdp_url/AUTHZEN_PDP_URL and the other occurrence around line 147) so no outbound
requests are made to invalid or non-http(s) targets.
- Around line 179-191: parseJSONArg currently accepts any JSON value; change it
to enforce that the parsed JSON is an object by unmarshaling into a generic
value (v) as it does now and then verifying v is a map[string]any (i.e. JSON
object); if not, return an error like "arg %q must be a JSON object". Apply this
validation in parseJSONArg for the documented AuthZEN args (subject, resource,
action, and optional context) so arrays/scalars are rejected early, and still
return json.RawMessage(s) on success (or nil for missing optional args).
- Around line 160-163: The code uses io.ReadAll(res.Body) in the PDP response
handling (variable raw) which is unbounded and can exhaust memory; replace the
direct io.ReadAll call with a bounded read using an io.LimitReader (or io.CopyN)
capped to a safe maximum (e.g. const maxPdpResponseBytes) before reading, and
handle the case where the body exceeds the limit by returning a proper error via
mcp.NewToolResultError; update the read error path to include both read errors
and the "response too large" condition so mcp.NewToolResultError is used
consistently.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Line 31: The CI workflow currently pins the linter to "version: latest" which
causes non-reproducible runs; replace the "version: latest" value used for the
golangci-lint step with a specific released tag (e.g., a stable semver like
v1.59.0) so the workflow always uses a known tool version, and update any
related references to the same tag (the "version: latest" literal in the
golangci-lint job/step) to ensure reproducible CI.

In @.github/workflows/release.yml:
- Line 35: The workflow step currently sets the GoReleaser action with the key
"version: latest"; update that to pin a specific released tag (or full SHA) of
the GoReleaser action instead of "latest" so releases are reproducible—locate
the workflow step containing the "version" key and change the value from
"latest" to a concrete tag like a vX.Y.Z release (or immutable commit SHA).

In `@main_test.go`:
- Around line 42-49: The fakePDP test server should assert the outbound HTTP
contract: inside the httptest handler created by fakePDP (the anonymous handler
passed to httptest.NewServer), validate r.Method == "POST" and that
r.Header.Get("Content-Type") contains "application/json", returning a
400/appropriate error if not, then read and json.Unmarshal the body into capture
as before; this ensures calls from evaluate() must be POST with JSON
content-type and will cause the test to fail if evaluate() stops sending proper
AuthZEN POST JSON requests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4b9cb31f-e938-4318-8ac0-c404550214fa

📥 Commits

Reviewing files that changed from the base of the PR and between 7901528 and 2d97a2e.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (6)
  • .github/workflows/ci.yml
  • .github/workflows/release.yml
  • .goreleaser.yml
  • go.mod
  • main.go
  • main_test.go

Comment thread .github/workflows/ci.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .goreleaser.yml Outdated
Comment thread main.go
Comment thread main.go Outdated
Comment thread main.go
kanywst added 7 commits May 27, 2026 23:34
- scripts/smoke.sh spins a Python fake PDP, runs mcp-authzen pointed at
  it, drives an MCP initialize -> tools/call(authzen_evaluate) sequence,
  and asserts the PDP's decision was forwarded back.
- Makefile targets: build, test, vet, lint, smoke, fmt, tidy, clean
- .github/dependabot.yml for gomod + github-actions (weekly, grouped)
- README rewritten: shorter; cites AuthZEN spec sections 6 / 5.5 it
  conforms to; documents tested-against-opa-authzen-plugin path.
- main.go: silence errcheck on response Body.Close()
- main.go: optional AUTHZEN_PDP_TOKEN env. Bare value auto-prefixed with
  "Bearer "; "Bearer " or "Basic " prefixes pass through verbatim
- main.go: io.LimitReader(1 MiB) on PDP response body to bound memory
- main.go: validate JSON args with json.Valid instead of a throwaway
  Unmarshal into any (idiomatic, fewer allocations)
- .goreleaser.yml: add windows/{amd64,arm64} build target; zip on windows
- workflows: pin actions to full commit SHAs and set
  persist-credentials: false on checkout (zizmor / CodeRabbit hardening)
- README: document AUTHZEN_PDP_TOKEN
- tests: cover bearer-token auto-prefixing and verbatim-prefix passthrough

(Gemini suggested downgrading go.mod to 1.23 because "Go 1.26 is not yet
released" — that was based on stale training data; Go 1.26.3 is the
current toolchain. Test step on Linux CI uses it without issue.)
- main.go: validate pdp_url before issuing the request — require absolute
  http(s) URL with a host. Closes the SSRF / arbitrary-target footgun
  when a model supplies pdp_url at call time.
- main.go: enforce JSON object for subject/resource/action/context args.
  Arrays and scalars previously slipped through and only failed at the
  PDP; AuthZEN §5 entities are objects, so reject earlier.
- .goreleaser.yml: switch `before.hooks` from `go mod tidy` to
  `go mod verify`. tidy can mutate go.mod / go.sum during release,
  breaking reproducibility. verify is read-only.
- tests: cover non-http scheme, relative URL, and array-as-subject
  rejection paths.
- smoke: build and run `make smoke` (in-process Python fake PDP, full
  MCP handshake, decision propagation assertion).
- vuln: govulncheck (Go reachable-CVE scan) + osv-scanner (OSV.dev cross-
  ecosystem scan).
- actionlint: lint the workflow files themselves.
- test: emit coverage with -covermode=atomic and print summary.

All actions pinned to SHAs with version comments for Dependabot tracking.
@kanywst kanywst merged commit 01fa3f4 into main May 27, 2026
6 checks passed
@kanywst kanywst deleted the feat/initial-scaffold branch May 27, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant